<?php
/**
 * Manager.php - 28 Jan 2009
 * 
 * The goal of this class is to create a general point of reference where all
 * config objects (and settings) can be stored and retrieved. 
 * 
 * [Todo:] 
 * Furthermore it aims to do some complex config retrieving functionality, 
 * regarding conventional and configurable settings of classes and objects.
 * A class property can behave as absolute default, which can be changed by a
 * configuration file, which behaves as new default. These defaults settings 
 * can be overridden by runtime changes on instantiated objects. Perhaps these
 * runtime changes can be simplified by adding some database config 
 * functionality, to keep track of the multiple settings of different objects.
 * 
 * This class implements the singleton design pattern, so only one instance can 
 * exist.
 * 
 * @author Kevin Breed
 */
 
class KB_Config_Manager
{
	/**
	 * @var KB_Config_Manager class instance
	 */
	protected static $_inst;
	
	/**
	 * @var string 	The seperator that will be used for seperating sections
	 */
	protected static $_sectionSeperator = '::';
	
	/**
	 * @var array	Array with stored config objects
	 */
	protected $_storedConfig = array();
	
	/**
	 * @var string 	The name of the default config object
	 */
	protected $_defaultName;
	
	
	/**************/
	/* EXCEPTIONS */
	/**************/
	
	const E_INVALID_CONFIGNAME = 1;
	
	
	/*********************/
	/* MACHINERY HANDLES */
	/*********************/
	
	/**
	 * Creates a new instance, or returns the (only) instance of itself if it is
	 * already instantiated.
	 * 
	 * @return  KB_Config_Manager 	An instance of itself.
	 */
	public static function getInstance ()
	{
		if ( !isset( self::$_inst ) ) self::$_inst = new self();
		
		return self::$_inst;
	}
	
	/**
	 * Sets the seperator that is used to distinguish nested settings.
	 *
	 * @var 	string 	An seperator to distinguish nested settings.
	 * @return	void
	 */
	public static function setSectionSeperator ( $seperator )
	{
		self::$_sectionSeperator = $seperator;
	}
	
	/**
	 * Adds a config object to this manager. When 'default' is given as $name, 
	 * the config object will act as the default to act upon.
	 * 
	 * @param 	array|Zend_Config 	$config		The config instance to store. Can 
	 *											be either a Zend_Config object 
	 *											or an associative array.
	 * @param 	string 				$name		A name for the config object. 
	 *											When nothing, or 'default' is 
	 * 											given the object will be used as 
	 * 											the default to retrieve values 
	 * 											from. 
	 * 
	 * @return 	void
	 */
	public function addConfig ( $config, $name, $setAsDefault=false )
	{
		$this->_storedConfig[ $name ] = $config;
		
		if ( count( $this->_storedConfig ) < 1 || $setAsDefault == true ) 
			 $this->setDefault( $name );
	}
	
	/**
	 * Removes a config object from the manager.
	 */
	public function removeConfig ( $name='default' )
	{
		if ( $name == 'default' )
			 unset( $this->_storedConfig[ $_defaultName ] );
		
		unset( $this->_storedConfig[ $name ] );
	}
	
	/**
	 * Returns a stored config instance
	 *
	 * @param 	string 				The name of the config instance. When no
	 * 								name is given the default config is returned
	 * @return 	array|Zend_Config 	The stored config
	 */
	public function getConfig ( $name='default' )
	{
		if ( $name == 'default' && 
			 isset( $this->_storedConfig[ $this->_defaultName ] ) )
			 return $this->_storedConfig[ $this->_defaultName ];
			 
		elseif ( isset( $this->_storedConfig[ $name ] ) )
			 return $this->_storedConfig[ $name ];
		
		else return null;
	}
	
	/**
	 * Set the config object as default.
	 */
	public function setDefault ( $name )
	{
		$this->_defaultName = $name;
	}
	
	/**
	 * Retrieves a setting from a stored config object.
	 * 
	 * @param	string $settingName		The name of the setting. Can be the full 
	 * 									path when the setting is stored in 
	 * 									(sub)sections
	 * @param 	mixed  $defaultValue  	An default value to return when the
	 * 									config option does not exists.
	 * @param 	string $confName		The name of the config object. When 
	 * 									ommitted the default object will be 
	 * 									called.
	 * @return	string					Returns the value of the given config 
	 * 									setting. This always is a final setting,
	 * 									and never a section.
	 */
	public function getConfigValue ( 	$settingName, 
										$defaultValue = null, 
										$confName = 'default' )
	{		
		if ( !isset( $this->_storedConfig[ $confName ] ) )
		{
			throw new Exception( 	"The specified config name '$confName' is " .
									"unknown. Use addConfig() to add a config " .
									"instance.",
									self::E_INVALID_CONFIGNAME );
			return;
		}
		
		if ( is_array( $this->_storedConfig[ $confName ] ) )
		{
			$ss 			= self::$_sectionSeperator;
			$cnfArrVar		= preg_replace
							  ( 
								"/{$ss}([^{$ss}]*)/", "['$1']", 
								$ss . $settingName 
							  );
			$arrVarStr 		= "\$this->_storedConfig[ '$confName' ]{$cnfArrVar}";
			$settingValue 	= null;
			
			// evaluate dynamic var
			eval( "if ( isset( $arrVarStr ) ) \$settingValue = $arrVarStr;" );
			
			// return it
			if ( $settingValue !== null ) return $settingValue;
		}
		
		elseif ( $this->_storedConfig[ $confName ] instanceof Zend_Config )
		{
			$vars 			= split( $this->_sectionSeperator, $settingName );
			$sectionNames 	= implode( '->', $vars );
			$finalVar		= array_pop( $vars );
			
			if ( isset( $this->_storedConfig[ $confName ]->$sectionNames ) )
				 return ( $defaultValue !== null ) 
						? 
						$this	->_storedConfig[ $confName ]
						 		->$sectionNames
						 		->get( $finalVar, $defaultValue ) 
						:
						$this	->_storedConfig[ $confName ]
						 		->$sectionNames
						 		->get( $finalVar );		
		}
		
		return $defaultValue;
	}
	
	
	/*************/
	/* MACHINERY */
	/*************/
	
	/**
	 * Constructor. Throws an error if this class is already instantiated.
	 */
	public function __construct ()
	{
		if ( isset( self::$_inst ) )
		{
			throw new Exception( 'Singleton class can only be instantiated ' 
								 . 'once. Please use the method getInstance ' 
								 . 'to retrieve the class instance.', 
								 self::E_INVALID_CONSTRUCTION );
			return;
		}
	}
}
 
?>
